﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SpeakupBackend.Themes;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using TableParsers;

namespace SpeakupTests.SpeakupBackend.Themes.Workshop
{
    [TestClass]
    public class WorkshopThemeCollectionTests
    {
        WorkshopThemeCollection OriginalThemes;
        WorkshopThemeCollection Themes;
        List<String> ThemeNames;

        [TestInitialize]
        public void TestInitialize()
        {
            ThemeNames = new List<string> ();
            for(int i = 0; i < 10; i++)
            {
                ThemeNames.Add( String.Format("ThemeName{0}",i));
            }
            
            Themes = new WorkshopThemeCollection();
            OriginalThemes = new WorkshopThemeCollection();

            var themeList = from ct in WorkshopClassType.GetWorkshopClassTypes()
                            from lg in LevelGroup.DefaultFactory.GetLevelGroups()
                      from wk in Week.GetWeeks()
                      from n in ThemeNames
                      select new WorkshopTheme(ct, lg, wk, n);

            foreach (WorkshopTheme th in themeList)
            {
                Themes.Add(th);
                OriginalThemes.Add(th);
            }            
        }

        [TestMethod]
        public void BinarySerialization()
        {
            MemoryStream stream = new MemoryStream(); 
            BinaryFormatter formatter = new BinaryFormatter();

            formatter.Serialize(stream, Themes);
            stream.Seek(0, SeekOrigin.Begin);
            WorkshopThemeCollection deserializedThemes = (WorkshopThemeCollection)formatter.Deserialize(stream);

            Assert.AreEqual(OriginalThemes.Count, deserializedThemes.Count);
            foreach (WorkshopTheme originalTheme in OriginalThemes)
            {
                Assert.IsTrue(deserializedThemes.Contains(originalTheme));
            }

            foreach (WorkshopTheme deserializedTheme in deserializedThemes)
            {
                Assert.IsTrue(OriginalThemes.Contains(deserializedTheme));
            }
        }

        [TestMethod]
        public void XmlSerialization()
        {
            MemoryStream stream = new MemoryStream();
            XmlSerializer formatter = new XmlSerializer(typeof(WorkshopThemeCollection));

            formatter.Serialize(stream, Themes);
            stream.Seek(0, SeekOrigin.Begin);
            WorkshopThemeCollection deserializedThemes = (WorkshopThemeCollection)formatter.Deserialize(stream);

            Assert.AreEqual(OriginalThemes.Count, deserializedThemes.Count);
            foreach (WorkshopTheme originalTheme in OriginalThemes)
            {
                Assert.IsTrue(deserializedThemes.Contains(originalTheme));
            }

            foreach (WorkshopTheme deserializedTheme in deserializedThemes)
            {
                Assert.IsTrue(OriginalThemes.Contains(deserializedTheme));
            }
        }

        [TestMethod]
        public void SaveAndLoad()
        {
            String tmpFile = Path.GetRandomFileName();
            
            Themes.Save(tmpFile);
            var deserializedThemes = WorkshopThemeCollection.Load(tmpFile);

            Assert.AreEqual(OriginalThemes.Count, deserializedThemes.Count);
            foreach (WorkshopTheme originalTheme in OriginalThemes)
            {
                Assert.IsTrue(deserializedThemes.Contains(originalTheme));
            }

            foreach (WorkshopTheme deserializedTheme in deserializedThemes)
            {
                Assert.IsTrue(OriginalThemes.Contains(deserializedTheme));
            }
        }


        [TestMethod]
        public void SaveAndReload()
        {
            String tmpFile = Path.GetRandomFileName();

            Themes.Save(tmpFile);
            Themes.ReloadFrom(tmpFile);

            Assert.AreEqual(OriginalThemes.Count, Themes.Count);
            foreach (WorkshopTheme originalTheme in OriginalThemes)
            {
                Assert.IsTrue(Themes.Contains(originalTheme));
            }

            foreach (WorkshopTheme theme in Themes)
            {
                Assert.IsTrue(OriginalThemes.Contains(theme));
            }
        }

        [TestMethod]
        public void SaveAndLoadXml()
        {
            String tmpFile = Path.GetRandomFileName();

            Themes.SaveXml(tmpFile);
            WorkshopThemeCollection deserializedThemes = WorkshopThemeCollection.LoadXml(tmpFile);
            
            Assert.AreEqual(OriginalThemes.Count, deserializedThemes.Count);
            foreach (WorkshopTheme originalTheme in OriginalThemes)
            {
                Assert.IsTrue(deserializedThemes.Contains(originalTheme));
            }

            foreach (WorkshopTheme deserializedTheme in deserializedThemes)
            {
                Assert.IsTrue(OriginalThemes.Contains(deserializedTheme));
            }
        }


        [TestMethod]
        public void SaveAndReloadXml()
        {
            String tmpFile = Path.GetRandomFileName();

            Themes.SaveXml(tmpFile);
            Themes.ReloadFromXml(tmpFile);

            Assert.AreEqual(OriginalThemes.Count, Themes.Count);
            foreach (WorkshopTheme originalTheme in OriginalThemes)
            {
                Assert.IsTrue(Themes.Contains(originalTheme));
            }

            foreach (WorkshopTheme theme in Themes)
            {
                Assert.IsTrue(OriginalThemes.Contains(theme));
            }
        }

        [TestMethod]
        public void MergeTwoDifferent()
        {
            WorkshopClassType ct1 = WorkshopClassType.GetWorkshopClassType(ClassType.Grammar);
            WorkshopClassType ct2 = WorkshopClassType.GetWorkshopClassType(ClassType.Grammar);
            
            Week wk1 = Week.GetWeek(1);
            Week wk2 = Week.GetWeek(2);
            
            LevelGroup lg1 = LevelGroup.DefaultFactory.GetLevelGroup(1);
            LevelGroup lg2 = LevelGroup.DefaultFactory.GetLevelGroup(2);
            
            String name1 = "name1";
            String name2 = "name2";            

            WorkshopTheme th1 = new WorkshopTheme(ct1, lg1, wk1, name1);
            WorkshopTheme th2 = new WorkshopTheme(ct2, lg2, wk2, name2);
            

            WorkshopThemeCollection c1 = new WorkshopThemeCollection();
            c1.Add(th1);

            WorkshopThemeCollection c2 = new WorkshopThemeCollection();
            c1.Add(th2);

            PrivateObject o = new PrivateObject(c1);
            o.Invoke("Merge", new Object [] { c2, false });

            Assert.AreEqual(2, c1.Count);

            Assert.IsTrue(c1.Contains(th1));
            Assert.IsTrue(c1.Contains(th2));

            Assert.AreEqual(name1, th1.Name);
            Assert.AreEqual(name2, th2.Name);
        }

        [TestMethod]
        public void MergeTwoSame()
        {
            WorkshopClassType ct1 = WorkshopClassType.GetWorkshopClassType(ClassType.Grammar);            
            Week wk1 = Week.GetWeek(1);
            LevelGroup lg1 = LevelGroup.DefaultFactory.GetLevelGroup(1);
            String name1 = "name1";
            WorkshopTheme th1 = new WorkshopTheme(ct1, lg1, wk1, name1);            
            
            WorkshopThemeCollection c1 = new WorkshopThemeCollection();
            c1.Add(th1);

            WorkshopThemeCollection c2 = new WorkshopThemeCollection();
            c1.Add(th1);

            PrivateObject o = new PrivateObject(c1);
            o.Invoke("Merge", new Object[] { c2, false });

            Assert.AreEqual(1, c1.Count);

            Assert.IsTrue(c1.Contains(th1));            
        }

        [TestMethod]
        public void MergeUseMine()
        {
            WorkshopClassType ct1 = WorkshopClassType.GetWorkshopClassType(ClassType.Grammar);
            Week wk1 = Week.GetWeek(1);
            LevelGroup lg1 = LevelGroup.DefaultFactory.GetLevelGroup(1);
            
            String name1 = "name1";
            String name2 = "name2";
            
            WorkshopTheme th1 = new WorkshopTheme(ct1, lg1, wk1, name1);
            WorkshopTheme th2 = new WorkshopTheme(ct1, lg1, wk1, name2);

            WorkshopThemeCollection c1 = new WorkshopThemeCollection();
            c1.Add(th1);

            WorkshopThemeCollection c2 = new WorkshopThemeCollection();
            c1.Add(th2);

            PrivateObject o = new PrivateObject(c1);
            o.Invoke("Merge", new Object[] { c2, false });

            Assert.AreEqual(1, c1.Count);
            Assert.AreEqual(name1, th1.Name);
            Assert.IsTrue(c1.Contains(th1));
            Assert.IsTrue(c1.Contains(th2));
        }

        [TestMethod]
        public void MergeUseTheirs()
        {
            WorkshopClassType ct1 = WorkshopClassType.GetWorkshopClassType(ClassType.Grammar);
            Week wk1 = Week.GetWeek(1);
            LevelGroup lg1 = LevelGroup.DefaultFactory.GetLevelGroup(1);

            String name1 = "name1";
            String name2 = "name2";

            WorkshopTheme th1 = new WorkshopTheme(ct1, lg1, wk1, name1);
            WorkshopTheme th2 = new WorkshopTheme(ct1, lg1, wk1, name2);

            WorkshopThemeCollection c1 = new WorkshopThemeCollection();
            c1.Add(th1);

            WorkshopThemeCollection c2 = new WorkshopThemeCollection();
            c2.Add(th2);

            PrivateObject o = new PrivateObject(c1);
            o.Invoke("Merge", new Object[] { c2, true });

            Assert.AreEqual(1, c1.Count);
            Assert.AreEqual(name2, th1.Name);

            Assert.IsTrue(c1.Contains(th1));
            Assert.IsTrue(c1.Contains(th2));
        }

        bool CollectionsAreEqual(WorkshopThemeCollection left, WorkshopThemeCollection right)
        {
            if (left.Count != right.Count)
            {
                return false;
            }

            foreach (var t in left)
            {
                if (!right.Contains(t))
                {
                    return false;
                }            
            }

            foreach (var t in right)
            {
                if (!left.Contains(t))
                {
                    return false;
                }
            }

            return true;
        }

        [TestMethod]
        public void LevelByIntFilter()
        {
            for (int i = Level.MinValidLevelNumber; i <= Level.MaxValidLevelNumber; i++)
            {
                WorkshopThemeCollection filtered = Themes.ApplyFilter(WorkshopThemeCollection.LevelFilter(i));
                WorkshopThemeCollection expected = new WorkshopThemeCollection();

                foreach (var th in Themes)
                {
                    if (th.LevelGroup.ContainsLevel(Level.GetLevel(i)))
                    {
                        expected.Add(th);
                    }
                }

                Assert.IsTrue(CollectionsAreEqual(expected, filtered));
            }            
        }

        [TestMethod]
        public void LevelFilter()
        {
            foreach (Level lvl in Level.GetLevels())
            {
                WorkshopThemeCollection filtered = Themes.ApplyFilter(WorkshopThemeCollection.LevelFilter(lvl));
                WorkshopThemeCollection expected = new WorkshopThemeCollection();

                foreach (var th in Themes)
                {
                    if (th.LevelGroup.ContainsLevel(lvl))
                    {
                        expected.Add(th);
                    }
                }

                Assert.IsTrue(CollectionsAreEqual(expected, filtered));
            }
        }

        [TestMethod]
        public void LevelGroupFilter()
        {
            foreach (LevelGroup lg in LevelGroup.DefaultFactory.GetLevelGroups())
            {
                WorkshopThemeCollection filtered = Themes.ApplyFilter(WorkshopThemeCollection.LevelFilter(lg));
                WorkshopThemeCollection expected = new WorkshopThemeCollection();

                foreach (var th in Themes)
                {
                    if (th.LevelGroup == lg)
                    {
                        expected.Add(th);
                    }
                }

                Assert.IsTrue(CollectionsAreEqual(expected, filtered));
            }
        }

        [TestMethod]
        public void ClassTypeFilter()
        {
            foreach(ClassType ct in Enum.GetValues(typeof(ClassType)))
            {
                WorkshopThemeCollection filtered = Themes.ApplyFilter(WorkshopThemeCollection.ClassTypeFilter(ct));
                WorkshopThemeCollection expected = new WorkshopThemeCollection();

                foreach (var th in Themes)
                {
                    if(th.ClassType.ClassType == ct)
                    {
                        expected.Add(th);
                    }
                }

                Assert.IsTrue(CollectionsAreEqual(expected, filtered));
            }
        }

        [TestMethod]
        public void WorkshopClassTypeFilter()
        {
            foreach (WorkshopClassType ct in WorkshopClassType.GetWorkshopClassTypes())
            {
                WorkshopThemeCollection filtered = Themes.ApplyFilter(WorkshopThemeCollection.ClassTypeFilter(ct));
                WorkshopThemeCollection expected = new WorkshopThemeCollection();

                foreach (var th in Themes)
                {
                    if (th.ClassType.ClassType == ct)
                    {
                        expected.Add(th);
                    }
                }

                Assert.IsTrue(CollectionsAreEqual(expected, filtered));
            }
        }

        [TestMethod]
        public void WeekByIntFilter()
        {
            for (int i = Week.MinValidWeekNumber; i <= Week.MaxValidWeekNumber; i++ )
            {
                WorkshopThemeCollection filtered = Themes.ApplyFilter(WorkshopThemeCollection.WeekFilter(i));
                WorkshopThemeCollection expected = new WorkshopThemeCollection();

                foreach (var th in Themes)
                {
                    if (th.Week.WeekNumber == i)
                    {
                        expected.Add(th);
                    }
                }

                Assert.IsTrue(CollectionsAreEqual(expected, filtered));
            }
        }

        [TestMethod]
        public void WeekFilter()
        {
            foreach (Week wk in Week.GetWeeks())
            {
                WorkshopThemeCollection filtered = Themes.ApplyFilter(WorkshopThemeCollection.WeekFilter(wk));
                WorkshopThemeCollection expected = new WorkshopThemeCollection();

                foreach (var th in Themes)
                {
                    if (th.Week == wk)
                    {
                        expected.Add(th);
                    }
                }

                Assert.IsTrue(CollectionsAreEqual(expected, filtered));
            }
        }

        [TestMethod]
        public void ClassTypeAndWeekFilter()
        {
            foreach (WorkshopClassType ct in WorkshopClassType.GetWorkshopClassTypes())
            {
                foreach (Week wk in Week.GetWeeks())
                {
                    WorkshopThemeCollection.ThemeFilter filter = WorkshopThemeCollection.ClassTypeAndWeekFilter(ct, wk);
                    WorkshopThemeCollection filtered = Themes.ApplyFilter(filter);
                    WorkshopThemeCollection expected = new WorkshopThemeCollection();

                    foreach (var th in Themes)
                    {
                        if (th.Week == wk && th.ClassType == ct)
                        {
                            expected.Add(th);
                        }
                    }

                    Assert.IsTrue(CollectionsAreEqual(expected, filtered));
                }
            }
        }

        [TestMethod]
        public void ClassTypeAndLevelFilter()
        {
            foreach (WorkshopClassType ct in WorkshopClassType.GetWorkshopClassTypes())
            {
                foreach (Level lv in Level.GetLevels())
                {
                    WorkshopThemeCollection.ThemeFilter filter = WorkshopThemeCollection.ClassTypeAndLevelFilter(ct, lv);
                    WorkshopThemeCollection filtered = Themes.ApplyFilter(filter);
                    WorkshopThemeCollection expected = new WorkshopThemeCollection();

                    foreach (var th in Themes)
                    {
                        if (th.LevelGroup.ContainsLevel(lv) && th.ClassType == ct)
                        {
                            expected.Add(th);
                        }
                    }

                    Assert.IsTrue(CollectionsAreEqual(expected, filtered));
                }
            }
        }

        [TestMethod]
        public void ClassTypeAndLevelGroupFilter()
        {
            foreach (WorkshopClassType ct in WorkshopClassType.GetWorkshopClassTypes())
            {
                foreach (LevelGroup lg in LevelGroup.DefaultFactory.GetLevelGroups())
                {
                    WorkshopThemeCollection.ThemeFilter filter = WorkshopThemeCollection.ClassTypeAndLevelFilter(ct, lg);
                    WorkshopThemeCollection filtered = Themes.ApplyFilter(filter);
                    WorkshopThemeCollection expected = new WorkshopThemeCollection();

                    foreach (var th in Themes)
                    {
                        if (th.LevelGroup == lg && th.ClassType == ct)
                        {
                            expected.Add(th);
                        }
                    }

                    Assert.IsTrue(CollectionsAreEqual(expected, filtered));
                }
            }
        }

        [TestMethod]
        public void WeekAndLevelFilter()
        {
            foreach (Week wk in Week.GetWeeks())
            {
                foreach (Level lv in Level.GetLevels())
                {
                    WorkshopThemeCollection.ThemeFilter filter = WorkshopThemeCollection.WeekAndLevelFilter(wk, lv);
                    WorkshopThemeCollection filtered = Themes.ApplyFilter(filter);
                    WorkshopThemeCollection expected = new WorkshopThemeCollection();

                    foreach (var th in Themes)
                    {
                        if (th.LevelGroup.ContainsLevel(lv) && th.Week == wk)
                        {
                            expected.Add(th);
                        }
                    }

                    Assert.IsTrue(CollectionsAreEqual(expected, filtered));
                }
            }
        }

        [TestMethod]
        public void WeekAndLevelGroupFilter()
        {
            foreach (Week wk in Week.GetWeeks())
            {
                foreach (LevelGroup lg in LevelGroup.DefaultFactory.GetLevelGroups())
                {
                    WorkshopThemeCollection.ThemeFilter filter = WorkshopThemeCollection.WeekAndLevelFilter(wk, lg);
                    WorkshopThemeCollection filtered = Themes.ApplyFilter(filter);
                    WorkshopThemeCollection expected = new WorkshopThemeCollection();

                    foreach (var th in Themes)
                    {
                        if (th.LevelGroup == lg && th.Week == wk)
                        {
                            expected.Add(th);
                        }
                    }

                    Assert.IsTrue(CollectionsAreEqual(expected, filtered));
                }
            }
        }

        [TestMethod]
        public void ByAllFilter()
        {
            foreach (WorkshopClassType ct in WorkshopClassType.GetWorkshopClassTypes())
            {
                foreach (Week wk in Week.GetWeeks())
                {
                    foreach (LevelGroup lg in LevelGroup.DefaultFactory.GetLevelGroups())
                    {
                        WorkshopThemeCollection.ThemeFilter filter = WorkshopThemeCollection.ByAllFilter(ct, lg, wk);
                        WorkshopThemeCollection filtered = Themes.ApplyFilter(filter);
                        WorkshopThemeCollection expected = new WorkshopThemeCollection();

                        foreach (var th in Themes)
                        {
                            if (th.LevelGroup == lg && th.Week == wk && th.ClassType == ct)
                            {
                                expected.Add(th);
                            }
                        }

                        Assert.IsTrue(CollectionsAreEqual(expected, filtered));
                    }
                }
            }
        }

        [TestMethod]
        public void ByAllRawFilter()
        {
            foreach (ClassType ct in Enum.GetValues(typeof(ClassType)))
            {
                for (int wki = Week.MinValidWeekNumber; wki <= Week.MaxValidWeekNumber; wki++ )
                {
                    for(int lvi = Level.MinValidLevelNumber; lvi <= Level.MaxValidLevelNumber; lvi++)
                    {
                        WorkshopThemeCollection.ThemeFilter filter = WorkshopThemeCollection.ByAllFilter(ct, lvi, wki);
                        WorkshopThemeCollection filtered = Themes.ApplyFilter(filter);
                        WorkshopThemeCollection expected = new WorkshopThemeCollection();

                        foreach (var th in Themes)
                        {
                            if (th.LevelGroup.ContainsLevel(Level.GetLevel(lvi)) && 
                                th.Week.WeekNumber == wki &&
                                th.ClassType.ClassType == ct)
                            {
                                expected.Add(th);
                            }
                        }

                        Assert.IsTrue(CollectionsAreEqual(expected, filtered));
                    }
                }
            }
        }
    }
}
